package com.clw.classfile.attribute.info;

import com.clw.classfile.BytesReader;

/**
 * Created by chengliwei on 2018/11/16.
 */
public class StackMapTable extends AttributeInfo {

    private short numberOfEntries;
    private StackMapFrame[] entries; // 顺序很重要

    public StackMapTable(BytesReader reader) {
        super(reader.readInt());
        this.numberOfEntries = reader.readShort();
        this.entries = new StackMapFrame[numberOfEntries];
        for (int i = 0; i < numberOfEntries; i++) {
            entries[i] = StackMapFrame.read(reader);
        }
    }

    private static class StackMapFrame {

//        protected byte frameType;

        static StackMapFrame read(BytesReader reader) {
            int frameType = reader.read();
            if (frameType <= 63) {
                return new SameFrame();
            } else if (frameType <= 127) {
                return new SameLocals1StackItemFrame(reader);
            } else {
                switch (frameType) {
                    case 247:
                        return new SameLocals1StackItemFrameExtended(reader);
                    case 248:
                    case 249:
                    case 250:
                        return new ChopFrame(reader);
                    case 251:
                        return new SameFrameExtended(reader);
                    case 252:
                    case 253:
                    case 254:
                        return new AppendFrame(frameType - 251, reader);
                    case 255:
                        return new FullFrame(reader);
                }
            }
            return new StackMapFrame();
        }

        private static class SameFrame extends StackMapFrame {
        /* 0-63 */
        }

        private static class SameLocals1StackItemFrame extends StackMapFrame {

            /* 64-127 */
            VerificationTypeInfo[] stack;

            SameLocals1StackItemFrame(BytesReader reader) {
                stack = new VerificationTypeInfo[1];
                stack[0] = VerificationTypeInfo.read(reader);
            }
        }

        /* 128-246 为未来使用预留 */
        private static class SameLocals1StackItemFrameExtended extends StackMapFrame {

            /* 247 */
            private short offsetDelta;

            SameLocals1StackItemFrameExtended(BytesReader reader) {
                this.offsetDelta = reader.readShort();
            }
            //VerificationTypeInfo stack[1]
        }

        private static class ChopFrame extends StackMapFrame {

            /* 248-250 */
            private short offsetDelta;

            public ChopFrame(BytesReader reader) {
                this.offsetDelta = reader.readShort();
            }
        }

        private static class SameFrameExtended extends StackMapFrame {

            /* 251 */
            private short offsetDelta;

            public SameFrameExtended(BytesReader reader) {
                this.offsetDelta = reader.readShort();
            }
        }

        private static class AppendFrame extends StackMapFrame {

            /* 252-254 */
            private short offset_delta;
            VerificationTypeInfo[] locals; //size = frameType - 251

            public AppendFrame(int size, BytesReader reader) {
                this.offset_delta = reader.readShort();
                this.locals = new VerificationTypeInfo[size];
                for (int i = 0; i < size; i++) {
                    locals[i] = VerificationTypeInfo.read(reader);
                }
            }


        }

        private static class FullFrame extends StackMapFrame {

            /* 255 */
            private short offsetDelta;
            private short numberOfLocals;
            VerificationTypeInfo[] locals;
            private short numberOfStackItems;
            VerificationTypeInfo[] stack;

            FullFrame(BytesReader reader) {
                this.offsetDelta = reader.readShort();
                this.numberOfLocals = reader.readShort();
                this.locals = new VerificationTypeInfo[numberOfLocals];
                for (int i = 0; i < numberOfLocals; i++) {
                    locals[i] = VerificationTypeInfo.read(reader);
                }
                this.numberOfStackItems = reader.readShort();
                this.stack = new VerificationTypeInfo[numberOfStackItems];
                for (int i = 0; i < numberOfStackItems; i++) {
                    stack[i] = VerificationTypeInfo.read(reader);
                }
            }
        }
    }


    private static class VerificationTypeInfo {

        public static VerificationTypeInfo read(BytesReader reader) {
            int type = reader.read();
            switch (type) {
                case 0:
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                    return new CommonVariableInfo();
                case 7:
                    return new ObjectVariableInfo(reader.readShort());
                case 8:
                    return new UninitializedVariableInfo(reader.readShort());
                default:
                    throw new RuntimeException("unknown variable info");
            }
        }

        private static class CommonVariableInfo extends VerificationTypeInfo {

        }

        private static class ObjectVariableInfo extends VerificationTypeInfo {

            short cPoolIndex;

            public ObjectVariableInfo(short cPoolIndex) {
                this.cPoolIndex = cPoolIndex;
            }
        }

        private static class UninitializedVariableInfo extends VerificationTypeInfo {

            short offset;

            public UninitializedVariableInfo(short offset) {
                this.offset = offset;
            }
        }
    }

    // 《Java虚拟机规范（Java SE 7版）》明确规定：在版本号大于或等于50.0的class文件中，
    // 如果方法的Code属性中没有附带StackMapTable属性，那就意味着它带有一个隐式的StackMap属性。
    // 这个StackMap属性的作用等同于number_of_entries值为0的StackMapTable属性。一个方法的Code
    // 属性最多只能有一个StackMapTable属性，否则将抛出ClassFormatError异常
}
